﻿using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Linq;
using GraphSharp.Algorithms.Layout;
using GraphSharp.Algorithms.Layout.Compound.FDP;
using GraphSharp.Algorithms.Layout.Simple.Hierarchical;
using QuickGraph;

namespace ttg_internal.Utilities.PipelineManager.Diagram
{
    /// <summary>
    /// Graph entity
    /// </summary>
    public class PipelineGraph : BidirectionalGraph<DesignerItem, Connection>
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public PipelineGraph()
            : base(false)
        {
            //
        }
    }

    public class DesignerCanvas : Canvas
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public DesignerCanvas()
        {
            AllowDrop = true;
        }

        // Dependency properties
        public readonly static DependencyProperty AutoAlignProperty = DependencyProperty.Register("AutoAlign", typeof(bool), typeof(DesignerCanvas), new PropertyMetadata(true, OnAutoAlignChanged));

        public bool AutoAlign
        {
            get { return (bool)GetValue(AutoAlignProperty); }
            set { SetValue(AutoAlignProperty, value); }
        }

        // start point of the rubberband drag operation
        private Point? _rubberbandSelectionStartPoint;

        // keep track of selected items 
        private ObservableCollection<ISelectable> _selectedItems;

        public ObservableCollection<ISelectable> SelectedItems
        {
            get
            {
                return _selectedItems ??
                       (_selectedItems = new ObservableCollection<ISelectable>());
            }
            set
            {
                _selectedItems = value;
            }
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Source == this)
            {
                // in case that this click is the start for a 
                // drag operation we cache the start point
                _rubberbandSelectionStartPoint = new Point?(e.GetPosition(this));

                // if you click directly on the canvas all 
                // selected items are 'de-selected'
                foreach (ISelectable item in SelectedItems.ToArray())
                {
                    item.IsSelected = false;
                }

                _selectedItems.Clear();

                e.Handled = true;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            // if mouse button is not pressed we have no drag operation, ...
            if (e.LeftButton != MouseButtonState.Pressed)
            {
                _rubberbandSelectionStartPoint = null;
            }

            // ... but if mouse button is pressed and start
            // point value is set we do have one
            if (_rubberbandSelectionStartPoint.HasValue)
            {
                // create rubberband adorner
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);

                if (adornerLayer != null)
                {
                    adornerLayer.Add(new RubberbandAdorner(this, _rubberbandSelectionStartPoint));
                }
            }

            e.Handled = true;
        }

        /*protected override void OnDrop(DragEventArgs e)
        {
            base.OnDrop(e);
            DragObject dragObject = e.Data.GetData(typeof(DragObject)) as DragObject;
            if (dragObject != null && !String.IsNullOrEmpty(dragObject.Xaml))
            {
                DesignerItem newItem = null;
                Object content = XamlReader.Load(XmlReader.Create(new StringReader(dragObject.Xaml)));

                if (content != null)
                {
                    newItem = new DesignerItem();
                    newItem.Content = content;

                    Point position = e.GetPosition(this);

                    if (dragObject.DesiredSize.HasValue)
                    {
                        Size desiredSize = dragObject.DesiredSize.Value;
                        newItem.Width = desiredSize.Width;
                        newItem.Height = desiredSize.Height;

                        DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X - newItem.Width / 2));
                        DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y - newItem.Height / 2));
                    }
                    else
                    {
                        DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X));
                        DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y));
                    }

                    this.Children.Add(newItem);

                    //update selection
                    foreach (ISelectable item in this.SelectedItems)
                        item.IsSelected = false;
                    SelectedItems.Clear();
                    newItem.IsSelected = true;
                    this.SelectedItems.Add(newItem);
                }

                e.Handled = true;
            }
        }*/

        /*protected override Size MeasureOverride(Size constraint)
        {
            Size size = new Size();

            foreach (UIElement element in base.Children)
            {
                double left = Canvas.GetLeft(element);
                double top = Canvas.GetTop(element);

                left = double.IsNaN(left) ? 0 : left;
                top = double.IsNaN(top) ? 0 : top;

                //measure desired size for each child
                element.Measure(constraint);

                Size desiredSize = element.DesiredSize;
                if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
                {
                    size.Width = Math.Max(size.Width, left + desiredSize.Width);
                    size.Height = Math.Max(size.Height, top + desiredSize.Height);
                }
            }

            // add margin 
            size.Width += 10;
            size.Height += 10;
            return size;
        }*/

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            if (AutoAlign && Children.Count > 0)
            {
                var vertices = Children.OfType<DesignerItem>();
                var edges = Children.OfType<Connection>();

                var graph = new PipelineGraph();
                graph.AddVertexRange(vertices);
                graph.AddEdgeRange(edges);

                ILayoutAlgorithm<DesignerItem, Connection, PipelineGraph> alg;

                if (edges.Any())
                {
                    alg = new StandardLayoutAlgorithmFactory<DesignerItem, Connection, PipelineGraph>()
                                .CreateAlgorithm("EfficientSugiyama",
                                                 new LayoutContext<DesignerItem, Connection, PipelineGraph>(graph, null, vertices.ToDictionary(vert => vert, vert => vert.DesiredSize), LayoutMode.Simple),
                                                 new EfficientSugiyamaLayoutParameters { LayerDistance = 50 });
                }
                else
                {
                    alg = new StandardLayoutAlgorithmFactory<DesignerItem, Connection, PipelineGraph>()
                    .CreateAlgorithm("CompoundFDP",
                                     new LayoutContext<DesignerItem, Connection, PipelineGraph>(graph, null, vertices.ToDictionary(vert => vert, vert => vert.DesiredSize), LayoutMode.Simple),
                                     new CompoundFDPLayoutParameters());
                }

                alg.Compute();

                double minX = 0, minY = 0;

                foreach (Point point in alg.VertexPositions.Values)
                {
                    if (point.X < minX)
                    {
                        minX = point.X;
                    }

                    if (point.Y < minY)
                    {
                        minY = point.Y;
                    }
                }

                foreach (DesignerItem designerItem in vertices)
                {
                    designerItem.Left = alg.VertexPositions[designerItem].X - minX;
                    designerItem.Top = alg.VertexPositions[designerItem].Y - minY;

                    designerItem.SetBinding(LeftProperty, new Binding(DesignerItem.LeftProperty.Name) { Source = designerItem, Mode = BindingMode.TwoWay });
                    designerItem.SetBinding(TopProperty, new Binding(DesignerItem.TopProperty.Name) { Source = designerItem, Mode = BindingMode.TwoWay });
                }

                AutoAlign = false;
            }

            return base.ArrangeOverride(arrangeSize);
        }

        private static void OnAutoAlignChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Debug.Assert(d is DesignerCanvas);
            
            if ((bool)e.NewValue)
            {
                ((DesignerCanvas)d).InvalidateArrange();
            }
        }
    }
}
